/*
 * Copyright 2022 Huawei Cloud Computing Technology Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.huawei.cloudphone.apiimpl;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.ActivityInfo;
import android.graphics.PixelFormat;

import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;

import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;

import com.huawei.cloudphone.BuildConfig;
import com.huawei.cloudphone.ICASAidlInterface;
import com.huawei.cloudphone.ICASAidlListener;
import com.huawei.cloudphone.api.CloudAppDataListener;
import com.huawei.cloudphone.api.CloudPhoneOrientationChangeListener;
import com.huawei.cloudphone.api.CloudPhoneParas;
import com.huawei.cloudphone.api.CloudPhoneParas.DisplayMode;
import com.huawei.cloudphone.api.CloudPhoneStateListener;
import com.huawei.cloudphone.api.CloudPhoneVirtualDevDataListener;
import com.huawei.cloudphone.api.ICloudPhone;
import com.huawei.cloudphone.common.CASLog;
import com.huawei.cloudphone.common.CasConnectorInfo;
import com.huawei.cloudphone.common.CasParcelableMap;
import com.huawei.cloudphone.common.CasState;
import com.huawei.cloudphone.service.CASClient;
import com.huawei.cloudphone.service.CASService;

import java.net.BindException;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;

import static android.view.KeyEvent.KEYCODE_BACK;
import static android.view.KeyEvent.KEYCODE_VOLUME_DOWN;
import static android.view.KeyEvent.KEYCODE_VOLUME_UP;
import static com.huawei.cloudphone.api.CloudPhoneParas.DisplayMode.DISPLAY_MODE_FILL;
import static com.huawei.cloudphone.common.CasState.CAS_CONNECT_EXCEPTION;
import static com.huawei.cloudphone.utils.CasMediaUtils.isValidMediaConfig;

public class CloudPhoneImpl implements ICloudPhone {
    private static final String TAG = "CloudPhoneImpl";
    private static final int STATE_INIT = 0;
    private static final int STATE_DEINIT = 1;
    private static final int STATE_STARTING = 2;
    private static final int STATE_START = 3;
    private static final int STATE_STOP = 4;
    private static final int STATE_PAUSE = 5;
    private static final int STATE_RESUME = 6;
    private static final int CMD_START = 0;
    private static final int CMD_STOP = 1;
    private static final int CMD_PAUSE = 2;
    private static final int CMD_RESUME = 3;
    private static final int CMD_SURFACE_CREATE = 11;
    private static final int CMD_SURFACE_DESTROY = 12;
    private static final int CMD_STATE_CHANGE = 14;
    private static final int CMD_BACKGROUND_TIMEOUT = 15;
    private static final int CMD_RECONNECT = 16;
    private static final int CMD_SET_MEDIA_CONFIG = 17;
    private static final byte CUSTOM_DATA = 18;
    private static final byte IME_DATA =14;

    private final Object mCloudPhoneLock = new Object();
    private CASClient mCASClient = null;
    private Map<String, String> mStartParas = null;
    private Context mContext = null;
    private Handler mCmdHandler = null;
    private HandlerThread mCmdHandlerThread = null;
    private Activity mActivity;
    private ViewGroup mViewGroup;
    private SurfaceView mSurfaceView = null;
    private int mDisplayWidth = 0;
    private int mDisplayHeight = 0;
    private int mCurrentState;
    private DisplayMode mDisplayMode = DISPLAY_MODE_FILL;
    private boolean mServiceConnect = false;
    private Timer mBackGroundTimer = null;
    private int mAutoReconnectCount = 0;
    private Timer mAutoReconnectTimer = null;
    private Thread mCheckConnectStateThread = null;
    private volatile boolean mIsReconnectTaskRun = false;
    private CloudPhoneStateListener mStateListener = null;
    private CloudPhoneOrientationChangeListener mOrientationChangeListener = null;
    private CloudPhoneVirtualDevDataListener mVirtualDevDataListener = null;
    private CloudAppDataListener mChannelDataListener = null;
    private ServiceConnection mConnection = null;
    private CASListener mListener = null;
    private volatile boolean mIsMute = false;
    private volatile boolean mIsStartSuccess = false;
    private CloudPhoneParas.DevType mDevType;
    private CasConnectorInfo mConnectorInfo = null;
    private boolean mIsNewCreated = false;
    private boolean mReconnecting = false;
    private int mBgTimeout = 60 * 1000;
    private int mTouchCount = 0;
    private VelocityTracker mVelocityTracker = VelocityTracker.obtain();

    private long initTime = 0;

    private CloudPhoneImeMgr mImeMgr;

    public CloudPhoneImpl() {
        mCurrentState = STATE_DEINIT;
    }

    @Override
    public String getVersion() {
        return BuildConfig.VERSION_NAME;
    }

    @Override
    public void init(Context context, CloudPhoneParas.DevType type) throws Exception {
        CASLog.i(TAG, "init called mCurrentState = " + mCurrentState);
        if (mCurrentState != STATE_DEINIT) {
            CASLog.e(TAG, "Not ready for init. current state is " + mCurrentState);
            throw new IllegalStateException("Not ready for init.");
        }
        mConnection = new CloudPhoneServiceConnection();
        mCASClient = new CASClient();
        mListener = new CASListener();
        mContext = context;
        mServiceConnect = false;
        Intent intent = new Intent(mContext, CASService.class);
        boolean ret = mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
        if (!ret) {
            CASLog.e(TAG, "Bind service failed.");
            throw new BindException("Bind service failed.");
        }
        mCmdHandlerThread = new HandlerThread("cmd process");
        if (mCmdHandlerThread == null) {
            CASLog.e(TAG,"Create handler thread failed.");
            throw new RuntimeException("Bind service failed.");
        }
        mCmdHandlerThread.start();
        mCmdHandler = new CmdHandler(mCmdHandlerThread.getLooper());
        mDevType = type;
        mCurrentState = STATE_INIT;
    }

    @Override
    public void deinit() throws Exception {
        if (mCurrentState == STATE_DEINIT) {
            return;
        }
        CASLog.i(TAG, "deinit called");
        mCmdHandlerThread.quit();
        mCmdHandlerThread.join();
        mCASClient.registerListener(null);
        mContext.unbindService(mConnection);
        mConnection = null;
        mCmdHandlerThread = null;
        mCurrentState = STATE_DEINIT;
        mCASClient = null;
        mContext = null;
        mStateListener = null;
        mOrientationChangeListener = null;
        mChannelDataListener = null;
        mListener = null;
    }

    @Override
    public void startCloudPhone(Activity activity, ViewGroup view, Map<String, String> params) {
        if (mCurrentState == STATE_DEINIT) {
            CASLog.e(TAG, "Call init first.");
            throw new IllegalStateException("Call init first.");
        }
        CASLog.i(TAG, "startCloudPhone called");
        mActivity = activity;
        mViewGroup = view;
        mStartParas = params;
        mConnectorInfo = new CasConnectorInfo();
        if (!mConnectorInfo.initConnectorParams(mStartParas)) {
            CASLog.e(TAG, "Init connector params failed.");
            throw new IllegalArgumentException("Init connector params failed.");
        }
        mBgTimeout = Integer.parseInt(mConnectorInfo.getBackgroundTimeout()) * 1000;
        mIsStartSuccess = false;
        Message msg = new Message();
        msg.what = CMD_START;
        mCmdHandler.sendMessage(msg);
    }

    @Override
    public void exitCloudPhone() throws Exception {
        CASLog.i(TAG, "exitCloudPhone called");
        synchronized (mCloudPhoneLock) {
            if ((mCurrentState == STATE_START) || (mCurrentState == STATE_PAUSE) || (mCurrentState == STATE_STARTING)) {
                CASLog.i(TAG, "exitCloudPhone begin...");
                mIsReconnectTaskRun = false;
                if (mCheckConnectStateThread != null) {
                    mCheckConnectStateThread.interrupt();
                    try {
                        mCheckConnectStateThread.join();
                    } catch (InterruptedException e) {
                        CASLog.e(TAG, "exit cloud phone failed. " + e.getMessage());
                    }
                    mCheckConnectStateThread = null;
                }
                if (mBackGroundTimer != null) {
                    mBackGroundTimer.cancel();
                    mBackGroundTimer = null;
                }
                mCASClient.stopJniRecv();
                mCASClient.stop(false);
                mCmdHandler = null;
                mCurrentState = STATE_INIT;
                mSurfaceView = null;
                mActivity = null;
                mViewGroup = null;
                mAutoReconnectCount = 0;
                CASLog.i(TAG, "exitCloudPhone end...");
                Intent intent = new Intent("com.huawei.cloudapp.ui.CasCloudPhoneActivity.destroyed");
                mContext.sendBroadcast(intent);
            }
        }
    }

    @Override
    public void mute() {
        if (mCASClient != null) {
            mCASClient.mute(true);
        }
        mIsMute = true;
    }

    @Override
    public void unmute() {
        if (mCASClient != null) {
            mCASClient.mute(false);
        }
        mIsMute = false;
    }

    @Override
    public void setMediaConfig(HashMap<String, String> mediaConfig) {
        if (mCmdHandler == null) {
            return;
        }
        if (!isValidMediaConfig(mediaConfig)) {
            CASLog.e(TAG, "media config is invalid");
            return;
        }
        Message msg = new Message();
        msg.what = CMD_SET_MEDIA_CONFIG;
        msg.obj = mediaConfig;
        mCmdHandler.sendMessage(msg);
    }

    @Override
    public void setDisplayMode(DisplayMode mode) {
        mDisplayMode = mode;
    }

    @Override
    public void sendDataToCloudApp(byte[] data) {
        if (mCASClient != null) {
            mCASClient.sendDataToCloudApp(data);
        }
    }

    @Override
    public void registerCloudAppDataListener(CloudAppDataListener listener) {
        mChannelDataListener = listener;
    }

    @Override
    public void registerCloudPhoneStateListener(CloudPhoneStateListener listener) {
        mStateListener = listener;
    }

    @Override
    public void registerOnOrientationChangeListener(CloudPhoneOrientationChangeListener listener) {
        mOrientationChangeListener = listener;
    }

    @Override
    public void registerOnVirtualDevDataListener(CloudPhoneVirtualDevDataListener listener) {
        mVirtualDevDataListener = listener;
    }

    @Override
    public void sendVirtualDeviceData(byte devType, byte[] data) {
        if (mCASClient != null) {
            mCASClient.sendDataToVirtualDevice(devType, data);
        }
    }

    @Override
    public int getRtt() {
        if (mCASClient != null) {
            return mCASClient.getLag();
        } else {
            return 0;
        }
    }

    public void handleStartCmd(final Activity activity, final ViewGroup views) {
        if (null == activity) {
            CASLog.e(TAG, "handle start cmd fail, activity is null.");
            return;
        }
        if (mCurrentState != STATE_INIT) {
            CASLog.e(TAG, "mCurrentState = " + mCurrentState);
            return;
        }
        CASLog.i(TAG, "handleStartCmd start");
        mCurrentState = STATE_STARTING;
        //创建SurfaceView
        if (mDisplayMode == DISPLAY_MODE_FILL) {
            mSurfaceView = new PhoneView(activity, true);
        } else {
            mSurfaceView = new PhoneView(activity, false);
        }

        mSurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                CASLog.i(TAG, "surfaceCreated...");
                mIsNewCreated = true;
            }

            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

                mDisplayWidth = Math.min(width, height);
                mDisplayHeight =  Math.max(width, height);
                CASLog.i(TAG, "width = " + mDisplayWidth + " height = " + mDisplayHeight);

                if (!mServiceConnect) {
                    mStateListener.onNotify(CasState.CAS_CONNECT_LOST, "Connect lost");
                    return;
                }

                if (!mIsNewCreated) {
                    return;
                }
                mIsNewCreated = false;
                synchronized (mCloudPhoneLock) {
                    if (mCmdHandler != null) {
                        Message msg = new Message();
                        msg.obj = holder;
                        msg.what = CMD_SURFACE_CREATE;
                        mCmdHandler.sendMessage(msg);
                    }
                }
            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                synchronized (mCloudPhoneLock) {
                    CASLog.i(TAG, "surfaceDestroyed...");
                    if (mCASClient.getState() != JNIState.JNI_STOPPED) {
                        mCASClient.stop(true);
                    }
                    if (mCmdHandler != null) {
                        Message msg = new Message();
                        msg.what = CMD_SURFACE_DESTROY;
                        mCmdHandler.sendMessage(msg);
                    }
                }
            }
        });
        //设置触摸事件监听

        mSurfaceView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                boolean result = true;
                final double ratio = 0.0125;
                int action = event.getActionMasked();
                int id;
                int rawX;
                int rawY;
                int pressure;
                int index;
                long time;
                if (action == MotionEvent.ACTION_DOWN) {
                    initTime = event.getEventTime();
                }
                switch (action) {
                    case MotionEvent.ACTION_DOWN:
                    case MotionEvent.ACTION_POINTER_DOWN:
                    case MotionEvent.ACTION_POINTER_UP:
                    case MotionEvent.ACTION_CANCEL:
                    case MotionEvent.ACTION_UP:
                        if (action == MotionEvent.ACTION_DOWN) {
                            mTouchCount = 1;
                        } else if (action == MotionEvent.ACTION_UP) {
                            mTouchCount ++;
                            CASLog.i(TAG, "mTouchCount = " +mTouchCount);
                        }
                        action = event.getActionMasked();
                        index = event.getActionIndex();
                        id = event.getPointerId(index);
                        pressure = (int) (event.getPressure(event.getActionIndex()) / ratio);
                        rawX = (int) event.getX(index);
                        rawY = (int) event.getY(index);
                        time = event.getEventTime() - initTime;
                        result = sendTouchEvent(id, action, rawX, rawY, pressure, time);
                        break;
                    case MotionEvent.ACTION_MOVE:
                        final int historySize = event.getHistorySize();
                        final int pointerCount = event.getPointerCount();
                        for (int i = 0; i < historySize; i++) {
                            for (int j = 0; j < pointerCount; j++) {
                                id = event.getPointerId(j);
                                rawX = (int) event.getHistoricalX(j, i);
                                rawY = (int) event.getHistoricalY(j, i);
                                pressure = (int) (event.getHistoricalPressure(j, i) / ratio);
                                time = event.getHistoricalEventTime(i) - initTime;
                                result = sendTouchEvent(id, action, rawX, rawY, pressure, time);
                                mTouchCount ++;
                            }
                        }
                        for (int i = 0; i < pointerCount; i++) {
                            id = event.getPointerId(i);
                            rawX = (int) event.getX(i);
                            rawY = (int) event.getY(i);
                            pressure = (int) (event.getPressure(i) / ratio);
                            time = event.getEventTime() - initTime;
                            result = sendTouchEvent(id, action, rawX, rawY, pressure, time);
                            mTouchCount ++;
                        }
                        break;
                    default:
                        CASLog.e(TAG, "unknown action");
                        break;
                }
                return result;
            }
        });

        if (mDevType == CloudPhoneParas.DevType.DEV_TV) {
            mSurfaceView.setOnKeyListener(new View.OnKeyListener() {
                @Override
                public boolean onKey(View v, int keyCode, KeyEvent event) {
                    if ((keyCode == KEYCODE_BACK)
                            || (keyCode == KEYCODE_VOLUME_DOWN)
                            || (keyCode == KEYCODE_VOLUME_UP)) {
                        return false;
                    }
                    CASLog.i(TAG, "keyCode = " + keyCode + " event = " + event.toString());
                    mCASClient.sendKeyEvent(keyCode, event.getAction());
                    return true;
                }
            });
        }

        //设置SurfaceView参数
        activity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                views.addView(mSurfaceView);
                mSurfaceView.setVisibility(View.VISIBLE);
                mSurfaceView.setClickable(true);
                mSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
                FrameLayout.LayoutParams layoutParams =
                        new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);
                mSurfaceView.setLayoutParams(layoutParams);

                mImeMgr = new CloudPhoneImeMgr(mContext, views);
                mImeMgr.setTextWatchListener(new TextWatchListener());
            }
        });
    }

    private void handleSurfaceCreateCmd(Message msg) {
        synchronized (mCloudPhoneLock) {
            mCASClient.setCasConnectorInfo(mConnectorInfo);
            CASLog.i(TAG, "handleSurfaceCreateCmd mCurrentState = " + mCurrentState);
            if (mCurrentState == STATE_STARTING) {
                mCASClient.setSurface(mSurfaceView.getHolder().getSurface());
                mCASClient.startJniRecv();
                if (mCASClient.start(false)) {
                    mIsReconnectTaskRun = true;
                    mCheckConnectStateThread = new CheckConnectStateThread();
                    mCheckConnectStateThread.start();
                }
                mCASClient.mute(mIsMute);
                mCurrentState = STATE_START;
            } else if (mCurrentState == STATE_PAUSE) {
                mCASClient.setSurface(mSurfaceView.getHolder().getSurface());
                mCASClient.startJniRecv();
                mCASClient.start(true);
                mCurrentState = STATE_START;
                if (mBackGroundTimer != null) {
                    mBackGroundTimer.cancel();
                    mBackGroundTimer = null;
                }
            } else {
                CASLog.e(TAG, "mCurrentState = " + mCurrentState);
            }
            CASLog.i(TAG, "handleSurfaceCreateCmd end");
        }
    }

    private void handleSurfaceDestroyCmd() {
        synchronized (mCloudPhoneLock) {
            if (mCurrentState == STATE_START) {
                CASLog.i(TAG, "handleSurfaceDestroyCmd end");
                mCASClient.pause();
                mCurrentState = STATE_PAUSE;
                mBackGroundTimer = new Timer();
                mBackGroundTimer.schedule(new BackgroundTimerTask(), mBgTimeout);
            }
        }
    }

    private void handleSetMediaConfigCmd(Message msg) {
        synchronized (mCloudPhoneLock) {
            HashMap<String, String> mediaConfig = (HashMap<String, String>) msg.obj;
            CasParcelableMap parcelableMap = new CasParcelableMap();
            parcelableMap.setParcelableMap(mediaConfig);
            mCASClient.setMediaConfig(parcelableMap);
        }
    }

    private void handleStateChangeCmd(Message msg) {
        //当cae返回如下这些状态时，sdk主动发起stop
        synchronized (mCloudPhoneLock) {
            int state = msg.arg1;
            if (state == CasState.CAS_BACKGROUND_TIMEOUT
                    || state == CasState.CAS_NOTOUCH_TIMEOUT
                    || state == CasState.CAS_TRAIL_PLAY_TIMEOUT
                    || state == CasState.CAS_VERIFY_FAILED
                    || state == CasState.CAS_VERIFY_DECRYPT_FAILED
                    || state == CasState.CAS_VERIFY_AESKEY_INVALID
                    || state == CasState.CAS_VERIFY_AESKEY_QUERY_FAILED
                    || state == CasState.CAS_VERIFY_PARAMETER_INVALID
                    || state == CasState.CAS_VERIFY_PARAMETER_MISSING
                    || state == CasState.CAS_CONNECT_LOST) {
                if (mCurrentState == STATE_START
                        || mCurrentState == STATE_PAUSE
                        || mCurrentState == STATE_STARTING) {
                    if (mCheckConnectStateThread != null) {
                        mIsReconnectTaskRun = false;
                        mCheckConnectStateThread.interrupt();
                        mCheckConnectStateThread = null;
                    }
                    mCASClient.stopJniRecv();
                    mCASClient.stop(false);
                }
            } else if (state == CasState.CAS_START_SUCCESS) {
                mIsStartSuccess = true;
            }
        }
    }

    private void handleBackgroundTimeoutCmd() {
        synchronized (mCloudPhoneLock) {
            if (mCurrentState == STATE_PAUSE) {
                if (mCheckConnectStateThread != null) {
                    mIsReconnectTaskRun = false;
                    try {
                        mCheckConnectStateThread.join();
                    } catch (InterruptedException e) {
                        CASLog.e(TAG, "handle background timeout failed. " + e.getMessage());
                    }
                    mCheckConnectStateThread = null;
                }
                mCASClient.stopJniRecv();
                mCASClient.stop(false);
                mCurrentState = STATE_INIT;
                mSurfaceView = null;
                mActivity = null;
                mViewGroup = null;
                if (mStateListener != null) {
                    mStateListener.onNotify(CasState.CAS_BACKGROUND_TIMEOUT,
                            "Switch background timeout");
                }
            }
        }
    }

    private void handleReconnectCmd() {
        if (!mIsStartSuccess) {
            mStateListener.onNotify(CasState.CAS_CONNECT_LOST, "Connect lost");
            return;
        }
        synchronized (mCloudPhoneLock) {
            if (mCurrentState == STATE_INIT || mCurrentState == STATE_PAUSE) {
                return;
            }
            CASLog.i(TAG, "reconnect mAutoReconnectCount=" + mAutoReconnectCount);
            mCASClient.stopJniRecv();
            if (mCASClient.reconnect()) {
                int status = mCASClient.getState();
                if (status == JNIState.JNI_CONNECTED) {
                    mCASClient.startJniRecv();
                    mAutoReconnectCount = 0;
                    mAutoReconnectTimer = null;
                    mReconnecting = false;
                    return;
                }
            }
            CASLog.i(TAG, "reconnect failed");
            mAutoReconnectCount++;
            if (mAutoReconnectCount > 3) {
                if (mStateListener != null) {
                    mStateListener.onNotify(CasState.CAS_CONNECT_LOST, "Connect lost");
                    mAutoReconnectCount = 0;
                    return;
                }
            }
            int time = 1;
            switch (mAutoReconnectCount) {
                case 1:
                    time = 1;
                    break;
                case 2:
                    time = 3;
                    break;
                case 3:
                    time = 5;
                    break;
                default:
                    return;
            }
            if (mAutoReconnectTimer != null) {
                mAutoReconnectTimer.cancel();
            }
            mAutoReconnectTimer = new Timer();
            mAutoReconnectTimer.schedule(new ReconnectTimerTask(), (long) time * 1000);
        }
    }

    private boolean sendTouchEvent(int id, int action, final int x1, final int y1, final int pressure, long time) {
        int orientation = 0;
        int newOrientation = 0;
        if (mActivity != null) {
            try {
                orientation = mActivity.getRequestedOrientation();
            } catch (Exception e) {
                CASLog.e(TAG, "get orientation failed. " + e.getMessage());
                return true;
            }
        }

        switch (orientation) {
            case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT:
            case ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT:
                newOrientation = 0;
                break;
            case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE:
            case ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE:
                newOrientation = 1;
                break;
            case ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT:
                newOrientation = 2;
                break;
            case ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE:
                newOrientation = 3;
                break;
            default:
                CASLog.i(TAG, "invalid directation " + orientation);
                return false;
        }

        int x = x1, y = y1;
        if (newOrientation == 1 || newOrientation == 3) {
            x = x1 * mDisplayWidth / mDisplayHeight;
            y = y1 * mDisplayHeight / mDisplayWidth;
        }
        return mCASClient.sendTouchEvent(id, action, x, y, pressure, time, newOrientation, mDisplayHeight, mDisplayWidth);
    }

    public static class JNIState {
        public static final int JNI_INIT = 0;
        public static final int JNI_LIB_INITIALIZED = 1;
        public static final int JNI_WORKER_THREADS_STARTED = 2;
        public static final int JNI_CONNECTED = 3;
        public static final int JNI_START = 4;
        public static final int JNI_START_FAILURE = 5;
        public static final int JNI_START_AUTH_FAILURE = 6;
        public static final int JNI_STOPPED = 9;
        public static final int JNI_CONNECTION_FAILURE = 10;
        public static final int JNI_INVALID = 15;
    }

    private class CmdHandler extends Handler {
        public CmdHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            while (!mServiceConnect) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    CASLog.e(TAG, "handle message failed. " + e.getMessage());
                }
            }
            switch (msg.what) {
                case CMD_START:
                    CASLog.i(TAG, "CMD_START");
                    handleStartCmd(mActivity, mViewGroup);
                    break;
                case CMD_SURFACE_CREATE:
                    CASLog.i(TAG, "CMD_SURFACE_CREATE");
                    handleSurfaceCreateCmd(msg);
                    break;
                case CMD_SURFACE_DESTROY:
                    CASLog.i(TAG, "CMD_SURFACE_DESTROY");
                    handleSurfaceDestroyCmd();
                    break;
                case CMD_SET_MEDIA_CONFIG:
                    CASLog.i(TAG, "CMD_SET_MEDIA_CONFIG");
                    handleSetMediaConfigCmd(msg);
                    break;
                case CMD_STATE_CHANGE:
                    CASLog.i(TAG, "CMD_STATE_CHANGE");
                    handleStateChangeCmd(msg);
                    break;
                case CMD_BACKGROUND_TIMEOUT:
                    CASLog.i(TAG, "CMD_BACKGROUND_TIMEOUT");
                    handleBackgroundTimeoutCmd();
                    break;
                case CMD_RECONNECT:
                    CASLog.i(TAG, "CMD_RECONNECT");
                    handleReconnectCmd();
                    break;
                default:
                    break;
            }
        }
    }

    /**
     * listener
     * <p>receive jni notify msg</p>
     */
    class CASListener extends ICASAidlListener.Stub {
        @Override
        public void onRotationDirectionChange(int orientation) throws RemoteException {
            synchronized (this) {
                if (mOrientationChangeListener != null) {
                    mOrientationChangeListener.onOrientationChange(orientation);
                }
            }
        }

        @Override
        public void onCmdRecv(int code, String describe) throws RemoteException {
            CASLog.i(TAG, "code = " + code + " msg = " + describe);
            Message msg = new Message();
            msg.what = CMD_STATE_CHANGE;
            msg.arg1 = code;
            mCmdHandler.sendMessage(msg);
            if (mStateListener != null) {
                mStateListener.onNotify(code, describe);
            }
        }

        @Override
        public void onChannelDataRecv(byte[] data) throws RemoteException {
            if (mChannelDataListener != null) {
                mChannelDataListener.onRecvCloudAppData(data);
            }
        }

        @Override
        public void onVirtualDevDataRecv(byte[] data) throws RemoteException {
            if (mVirtualDevDataListener != null) {
                mVirtualDevDataListener.onRecvVirtualDevData(data, data.length);
            }
        }

        public void onImeMsgRecv(byte[] data) throws RemoteException {
            if (mImeMgr != null) {
                mImeMgr.processImeMsg(data);
            }
        }
    }

    private class BackgroundTimerTask extends TimerTask {
        @Override
        public void run() {
            CASLog.i(TAG, "BackgroudTimerTask run");
            synchronized (mCloudPhoneLock) {
                if (mCmdHandler != null) {
                    Message msg = new Message();
                    msg.what = CMD_BACKGROUND_TIMEOUT;
                    CASLog.i(TAG, "CMD_BACKGROUND_TIMEOUT send");
                    mCmdHandler.sendMessage(msg);
                }
            }
        }
    }

    private class ReconnectTimerTask extends TimerTask {
        @Override
        public void run() {
            if (mCmdHandler != null) {
                Message msg = new Message();
                msg.what = CMD_RECONNECT;
                mCmdHandler.sendMessage(msg);
            }
        }
    }

    private class CheckConnectStateThread extends Thread {
        @Override
        public void run() {
            CASLog.i(TAG, "CheckConnectStateThread in");
            mReconnecting = false;
            while (mIsReconnectTaskRun) {
                if (!mReconnecting && (mCurrentState != STATE_PAUSE)) {
                    boolean isConnected = mCASClient.isConnect();
                    if (!isConnected) {
                        Message msg = new Message();
                        msg.what = CMD_RECONNECT;
                        mCmdHandler.sendMessage(msg);
                        mReconnecting = true;
                        mStateListener.onNotify(CAS_CONNECT_EXCEPTION, "Connection exception");
                    }
                }
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    CASLog.e(TAG, "check connect state failed. " + e.getMessage());
                }
            }
            CASLog.i(TAG, "CheckConnectStateThread out");
        }
    }

    class CloudPhoneServiceConnection implements ServiceConnection {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            CASLog.i(TAG, "onServiceConnected called");
            ICASAidlInterface casInterface = ICASAidlInterface.Stub.asInterface(service);
            mCASClient.setInterface(casInterface);
            mCASClient.init();
            mCASClient.registerListener(mListener.asBinder());
            mServiceConnect = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            CASLog.i(TAG, "onServiceDisconnected called");
            mCASClient.setInterface(null);
            mServiceConnect = false;
        }
    }

    class TextWatchListener implements CloudPhoneTextWatchListener {
        @Override
        public void onTextChange(byte[] data) {
            mCASClient.sendDataToIme(data);
        }
    }

}